<?php
/**
 * DMFramework
 * 
 * @license Copyright 2012 Dillen Meijboom under the Apache license version 2.0 (http://apache.org/licenses/LICENSE-2.0)
 * @author Dillen Meijboom <info@dillenm.nl>
 * @url http://dmframework.nl
 */

/**
 * The database-table mapper can be used to simplify database-actions, it looks kinda
 * like active-record but there are a lot of differences.
 * 
 * @license Copyright 2012 Dillen Meijboom under the Apache license version 2.0 (http://apache.org/licenses/LICENSE-2.0)
 * @author Dillen Meijboom <info@dillenm.nl>
 * @package System.Library.Database
 */
abstract class Dmf_Database_Table
{
    /**
     * Table name                       
     * 
     * @var string
     */
    protected $_table;
    
    /**
     * ID name of the column
     * 
     * @var string
     */
    protected $_idColumn = 'id';

    /**
     * Variable memory
     * 
     * @var array
     */
    private $_memory = array();

    /**
     * Id-number from table
     * 
     * @var int
     */
    protected $_id;

    /**
     * Deprecated!
     * 
     * @deprecated
     */
    protected $_connectionName;
    
    /**
     * The relations
     * 
     * @var array
     */
    protected $_relations = array();

    /**
     * Connection name
     * 
     * @var string 
     */
    protected $_connection;

    /**
     * The database adapter
     * 
     * @var Dmf_Database_Adapter_Mysql
     */
    private $_dbAdapter;

    /**
     * Constructor
     * 
     * @param int|array $id
     */
    public function __construct( $id = false )
    {
        $db = new Dmf_Database_Adapter( ( $this->_connectionName == null ) ? $this->_connection : $this->_connectionName );
        $this->_dbAdapter = $db->getAdapter();

        //Id id is false this model has to be created
        if ( $id === false )
        {
            return;
        }

        if ( is_array( $id ) )
        {
            $where = '';
            $values = array( );

            foreach( $id as $key => $value )
            {
                $where .= ' AND `' . $key . '`=?';
                $values[] = $value;
            }

            $row = $this->_dbAdapter->fetchRow( "SELECT `" . $this->_idColumn . "` FROM `" . $this->_table . "` WHERE " . substr( $where , 4 ) , $values );

            if ( ! isset( $row[ $this->_idColumn ] ) )
            {
                return false;
            }

            $this->_id = $row[ $this->_idColumn ];
        }
        else
        {
            $this->_id = $id;
        }

        //Update info
        $this->update();
    }
    
    /**
     * Get reference model
     * 
     * @param string $name
     * @return class|null 
     */
    public function getReference( $name )
    {
        foreach( $this->_relations as $relation )
        {
            if ( $relation['reference_model'] == $name )
            {
                $class = ucfirst( $relation['reference_model'] ) . 'Model';
                $key = $relation['primary_key'];
                $model = new $class( array(
                    $relation['reference_key'] => $this->$key
                ) );
                
                return $model;
                
                break;
            }
        }
        
        return null;
    }
    
    /**
     * Get reference models
     * 
     * @param string $name
     * @return class|null 
     */
    public function getReferences( $name )
    {
        foreach( $this->_relations as $relation )
        {
            if ( $relation['reference_model'] == $name )
            {
                $class = ucfirst( $relation['reference_model'] ) . 'Model';
                $key = $relation['primary_key'];
                
                return $class::each( array(
                    $relation['reference_key'] => $this->$key
                ) );
                
                break;
            }
        }
        
        return null;
    }
    
    /**
     * Get database adapter
     * 
     * @return type 
     */
    public function getDb()
    {
        return $this->_dbAdapter;
    }

    /**
     * Update model memory
     * 
     * @param string $table
     * @param string $idColumn
     */
    public function update()
    {
        $row = $this->_dbAdapter->fetchRow( "SELECT * FROM `" . $this->_table . "` WHERE `" . $this->_idColumn . "`=?" , $this->_id );
        
        if ( $row !== false )
        {
            foreach( $row as $index => $value )
            {
                $this->_memory[ $index ] = $value;
            }
        }
    }

    /**
     * Duplicate model
     * 
     * @param int $id
     */
    public function duplicate( $id )
    {
        $tempId = $this->_id;

        $this->_id = $id;

        $this->save();

        $this->_id = $tempId;
    }

    /**
     * Set the memory
     * 
     * @param array $memory 
     */
    public function setData( $memory )
    {
        $this->_memory = $memory;
    }
    
    /**
     * Save properties to mysql table
     */
    public function save()
    {
        if ( $this->_id === false || $this->_id == null )
        {
            return $this->create();
        }
        
        $query = 'UPDATE `' . $this->_table . '` SET ';
        $values = array();
        
        foreach( $this->_memory as $index => $value )
        {
            if ( $index == $this->_idColumn )
            {
                continue;
            }
            
            $query .= '`' . $index . '`=?, ';
            $values[] = $value;
        }
        
        $query = substr( $query, 0, -2 );
        
        $query .= ' WHERE `' . $this->_idColumn . '`=?';
        
        $values[] = $this->_id;
        
        $values = array_reverse( $values );
        $values[] = $query;
        $values = array_reverse( $values );
        
        call_user_func_array( array( $this->_dbAdapter, 'query' ), $values );
    }
    
    /**
     * The non-static database actions
     * 
     * @param string $method
     * @param array $args
     * @return mixed
     * @throws Exception 
     */
    public function __call( $method, $args )
    {
        switch( $method )
        {
            case 'exist':
                $key = $this->_idColumn;
                return ! ( $this->$key == null && count( $this->getData() ) == 0 );
                break;
            case 'delete':
                return $this->_dbAdapter->query( "DELETE FROM `" . $this->_table . "` WHERE `" . $this->_idColumn . "`=?" , $this->_id );
                break;
        }
        
        throw new Exception( 'Unknown active-record action: "' . $method . '".' );
    }
    
    /**
     * The static database actions
     * 
     * @param string $method
     * @param array $args
     * @return mixed
     * @throws Exception 
     */
    public static function __callStatic( $method, $args )
    {
        if ( ! function_exists( 'get_called_class' ) )
        {
            throw new Exception( 'PHP 5.3 or higher is needed for active record actions (static).' );
        }
        
        $class = get_called_class();
        $iterator = new Dmf_Database_Table_Iterator;
        
        $iterator->getTable( $class );
        $adapter = $iterator->getDatabaseAdapter();
        
        switch( $method )
        {
            case 'createUnique':
                $model = new $class( $args['0'] );
                
                if ( $model->exist() )
                {
                    return $model;
                }
                
                $model->setData( $args['0'] );
                $model->create();
                
                return $model;
                break;
            case 'find':
                if ( isset( $args['0'] ) )
                {
                    $iterator->search( $args['0'] );
                }
                
                return $iterator->execute()->getResult();
                break;
            case 'iterate':
                return $iterator;
                break;
            case 'query':
                if ( ! isset( $args['1'] ) )
                {
                    return $iterator->addQuery( $args['0'] )->execute()->getResult();
                }
                
                return $iterator->addQuery( $args['0'], $args['1'] )->execute()->getResult();
                break;
            case 'each':
                if ( isset( $args['0'] ) )
                {
                    $iterator->filter( $args['0'] );
                }
                
                return $iterator->execute()->getResult();
                break;
            case 'delete':
                if ( isset( $args['0'] ) )
                {
                    $iterator->filter( $args['0'] );
                }
                
                $results = $iterator->execute()->getResult();
                
                if ( is_object( $results ) )
                {
                    $results->delete();
                }
                
                return $results;
                break;
        }
        
        throw new Exception( 'Unknown active-record action: "' . $method . '".' );
    }

    /**
     * Create new instance
     * 
     * This methods inserts 1 column and saves the model.
     * 
     * @param int $id
     */
    public function create( $id = false )
    {
        if ( $id !== false )
        {
            $this->_memory[ $this->_idColumn ] = $id;
        }
        
        $indexes = '';

        foreach( $this->_memory as $index => $value )
        {
            $indexes .= '`' . $index . '`,';
        }
        
        $this->_dbAdapter->query( "INSERT INTO `" . $this->_table . "` (" . substr( $indexes , 0 , -1 ) . ") VALUES(" . substr( str_repeat( '?,' , count( $this->_memory ) ) , 0 , -1 ) . ")", $this->_memory );

        $this->_id = $this->_dbAdapter->getConnection()->lastInsertId();
        $this->save();
        $this->update();
    }
    
    /**
     * If this row is not valid
     * 
     * @return bool
     */
    public function isValid()
    {
        return ( $this->_id !== false );
    }

    /**
     * Get propery
     * 
     * @param string $index
     * @return mixed $_memory_$index
     */
    public function __get( $index )
    {
        $method = $index . 'GetProperty';
        
        if ( method_exists( $this, $method ) )
        {
            return $this->$method( ( isset( $this->_memory[ $index ] ) ? $this->_memory[ $index ] : null ) );
        }
        
        return isset( $this->_memory[ $index ] ) ? $this->_memory[ $index ] : null;
    }

    /**
     * Set property
     * 
     * @param string $index
     * @param mixed $value
     * @return mixed $_memory_$index
     */
    public function __set( $index , $value )
    {
        $method = $index . 'SetProperty';
        
        if ( method_exists( $this, $method ) )
        {
            $value = $this->$method( $value );
        }
        
        $this->_memory[ $index ] = $value;
    }

    /**
     * Get all memory data
     * 
     * @return array 
     */
    public function getData()
    {
        return $this->_memory;
    }

    /**
     * Get table info
     * 
     * @return array 
     */
    public function getInfo()
    {
        return array(
            'table' => $this->_table ,
            'id_column' => $this->_idColumn ,
            'con_name' => $this->_connectionName
        );
    }

}
